Title Banner

Previous Book Contents Book Index Next

Inside Macintosh: OpenDoc Programmer's Guide / Part 2 - Programming
Chapter 8 - Data Transfer


Drag and Drop

The OpenDoc drag-and-drop facility allows users to move or copy data through direct manipulation. Users can drag items from one location in a part or document (or the desktop) to another location in the same or a different part or document (or to the desktop).

Drag-and-Drop Concepts

Drag and drop provides a direct-manipulation alternative to the clipboard. The fundamental user-level operations--copying and moving--are similar in drag and drop and in clipboard transfer, and they are similar across all platforms.

User Interaction

The user typically initiates a drag by positioning the mouse pointer over some selected (or single-click-selectable) content, pressing and holding down the mouse button, and then moving the pointer. The user can hold down a modifier key while pressing the mouse button or while releasing the mouse button to force a drag to be a copy (called a drag-copy) or a move (called a drag-move).

As the user moves the mouse pointer, an outline of the selected item (provided by the source part, drawn by OpenDoc) is dragged to the new location. When the user releases the mouse button, the item is placed (dropped) at the pointer location. The part beneath the pointer is notified that something has been dropped on it. At this point, the destination part behaves essentially as it does when pasting from the clipboard; it makes the same decisions regarding embedding versus incorporating data and handling links. The source part may have to delete the items if the operation was a move, or do nothing if it was a copy.

Parts can restrict the dropped content they will accept to specific part kinds. To indicate that it can accept a drop, a part provides appropriate feedback to the user once the pointer enters its facet.

For frames and icons that are being dragged, OpenDoc provides specific behavior and appearance for the item being dragged. For intrinsic content that is being dragged, your part editor is responsible for defining behavior and providing user feedback. You should follow the guidelines given in the section "Using Drag and Drop".

The user can move an active frame by dragging its border. By moving the mouse pointer to the active frame border and pressing the mouse button, the user selects the frame and can start dragging immediately.

The user can move a selected frame by moving the pointer anywhere within it and pressing the button to start a drag. Also, if a frame is bundled, the user can drag it by pressing the mouse button while the pointer is anywhere in the interior of the frame, whether or not the frame is already active or selected.

When the user releases the mouse button, the frame and its part are dropped at the new location. After a frame is dropped, it becomes selected. The destination may adjust the drop location of a frame to reflect constraints such as gridding (in a graphics part) or text position (in a text part).

Move Versus Copy

OpenDoc follows these conventions for drag and drop:

OpenDoc supports a mechanism to allow the user to force a copy or move operation by pressing modifier keys. See "Force-Move and Force-Copy"

Dragging stationery
Stationery parts may be copied exactly like other parts. However, if you drag-move stationery into a part whose preferred view type for embedded parts is frame view, a copy is "torn off" the stationery pad and displayed in a frame. The stationery part remains in its original location, unchanged.

Droppable Frames

Each frame has a SetDroppable method, through which the part displayed in the frame controls whether or not the frame can (in general) accept dropped data. When a display frame is added or reconnected to your part, you can call SetDroppable and pass either kODTrue or kODFalse to allow or prohibit dropping. (When initially created, frames are not droppable.) You can call SetDroppable again at any time to change the state of the frame.

OpenDoc calls the IsDroppable method of the frame before making any of the drag-related method calls DragEnter, DragWithin, DragLeave, and Drop to your part. If your display frame is not droppable, your part does not receive any of these calls.

Your part's display frame does not have to be droppable for you to initiate a drag from it.

Undo for Drag and Drop

If your part supports dragging and dropping data, it must also support undoing that operation. (Note that data transfer between parts is undoable only if both parts involved support undo.) Undo support in general is described in the section "Undo".

For drag and drop, undo involves a multistage action. The part initiating the drag begins the action, the part receiving the drop adds a single action, and the initiating part completes the action when the drop is complete. See the section "Adding Multistage Actions" for more information.

If drag and drop involves embedded frames, be sure to set the dragged frames' in-limbo flags appropriately when initiating a drag, when dropping, and when the drag completes. See Table 6-4

Initiating a Drag

In response to a mouse-down event at the appropriate location, the part receiving the mouse event (the source part) has the choice of initiating a drag.

You should follow the procedures listed under "Handling Mouse-Down Events" in deciding whether to initiate a drag operation. On the Mac OS platform, you can call the Drag Manager function WaitMouseMoved, which returns true if the mouse pointer has moved enough to trigger a drag operation. (WaitMouseMoved returns false if a mouse-up occurs without the mouse pointer having moved sufficiently.)

To initiate a drag, your part (the source part) should follow these steps:

  1. Call the GetDragAndDrop method of the session object to gain access to
    the ODDragAndDrop object. Then call the Clear method and the GetContentStorageUnit method of the drag-and-drop object to get an empty storage unit into which to copy the dragged data.

    Note that there is no drag-and-drop focus or lock to be acquired. Only one part at a time can initiate and complete a drag. Version 1.0 of OpenDoc on the Mac OS platform does not support asynchronous dragging.

  2. Write the dragged data (or write a promise) into the drag-and-drop
    storage unit:

    • If the data is intrinsic content--with or without one or more embedded parts--follow the procedure outlined in the section "Writing Intrinsic Content".
    • If the data is a single embedded part, follow the procedure outlined in the section "Writing a Single Embedded Part".

      In either case, when you call the BeginClone method, specify kODCloneCut even if you are initiating a drag-copy. (The user can override the move-
      versus-copy conventions when dropping the data.)

  3. Create a property with the name kODPropMouseDownOffset in the drag-and-
    drop storage unit. Write into the property a value (of type kODPoint) that specifies the offset of the mouse from the origin (upper-left corner on the Mac OS platform) of the selection or item being dragged. This offset allows the destination part to locate the item correctly in relation to the mouse-up event when the item is dropped.
  4. If there are frames that should not accept a drop from this drag--such as any frames that are themselves being dragged--call the frames' SetDragging method and pass it a value of kODTrue to notify OpenDoc that it should not allow them to be drop targets.
  5. If the drag involves an embedded frame, follow the instructions listed in Table 6-4; set the frame's in-limbo flag to true if a drag-move is possible.
  6. Add a beginning action to the undo action history (see "Adding Multistage Actions"), to allow the user to undo the drag if necessary.
  7. Initiate the drag by calling the drag-and-drop object's StartDrag method. When you call StartDrag, you are responsible for providing OpenDoc an image, such as an outline, for it to display to the user as dragging feedback. (On the Mac OS, you provide a handle to a drag region in global coordinates, as required by the Drag Manager.)

The StartDrag method completes when the drop occurs; see "Completion of StartDrag"

Operations While a Drag Is in Progress

As long as the mouse button remains pressed, the drag is in progress. Potential destination parts need to perform certain actions during dragging, when the pointer is within their facets.

On Entering a Part's Facet

Any facet that the mouse pointer passes over during a drag represents a potential drop destination, if the facet's frame is droppable. OpenDoc calls a part's DragEnter method when the pointer enters one of its droppable frames' facets during a drag.

ODDragResult DragEnter(in ODDragItemIterator dragInfo,
                       in ODFacet facet,
                       in ODPoint where);
On receiving a call to its DragEnter method, your part (the potential destination part) should take these steps:

  1. Examine the part kinds of the dragged data, using a drag-item iterator (class ODDragItemIterator) passed to your part. Inspect the part kinds in each item's storage unit and determine whether or not you can accept the dragged data. An iterator is necessary in case non-OpenDoc data is being dragged; see "Accepting Non-OpenDoc Data". Apply these tests:

    • If your part cannot accept all the dragged items, it should not accept a drop at all.
    • Getting data from a drag-item iterator is a potentially time consuming; avoid it as much as possible during dragging. If your part is a container and can accept any data, including non-OpenDoc data, you do not need to check the items in the drag-item iterator. In this case, assume you can accept the data.
    • Your part's content model may allow only a limited number of items to be dragged. If so, assume that you cannot accept the data if the number of items in the drag-and-drop object exceeds that limit.
    • Check your draft's permissions. If your draft has read-only permission and accepting the drag will change persistent storage, assume that you cannot accept the data. See "Drafts" for more information on draft permissions.

  2. If your part can accept a drop, provide the appropriate feedback to the user, such as adorning your part's frame border or changing the cursor appearance. See "Destination Feedback (for Drag and Drop)"

    When the user initiates a drag from within your frame, you should not display destination feedback as long as the mouse pointer has not yet left your frame, although you should provide feedback if the mouse pointer returns to your frame after having left it. To help you know when to display feedback, OpenDoc on the Mac OS platform defines the drag attribute kODDragIsInSourceFrame (see "Drag Attributes and the Drop Method"), which, if returned, means that the mouse pointer has not yet left the source frame.

    You can draw the drag feedback on your own or with the help of platform-specific services such as the Mac OS Drag Manager's ShowDragHilite function.

  3. Examine the current state of the machine, if desired, to obtain any relevant information (such as whether the user has pressed a modifier key since initiating the drag).

If you return kODFalse from this method, OpenDoc assumes you cannot accept a drop and will not subsequently call your DragWithin, DragLeave, or Drop methods as long as the mouse pointer remains in this facet.

While Within a Part's Facet

OpenDoc calls the potential destination part's DragWithin method continuously while the mouse pointer remains inside the facet.

ODDragResult DragWithin(in ODDragItemIterator dragInfo,
                        in ODFacet facet,
                        in ODPoint where);
In response, the part can do any desired processing inside its display. For example, if a part allows objects to be dropped only in individual hot spots, it may change its feedback based on the pointer location.

Calls to a part's DragWithin method also give the part an additional chance to examine the state of the user's system. For example, the part may want to find out whether the user has pressed a modifier key during the drag operation.

If you return kODFalse from this method, OpenDoc assumes you no longer wish to accept a drop in this facet. It will not make additional calls to your DragWithin method and will not call your DragLeave or Drop method as long as the mouse pointer remains in this facet.

On Leaving a Part's Facet

OpenDoc calls the DragLeave method of a potential destination part when the mouse pointer leaves a droppable facet.

void DragLeave(in ODFacet facet,
               in ODPoint where);
In response, the part might remove the adornment on its frame or restore the cursor appearance to its original form.

Dropping

If the user releases the mouse button while the pointer is within a facet, OpenDoc calls the Drop method of the facet's part. This is its interface:

ODDropResult Drop(in ODDragItemIterator dropInfo,
                  in ODFacet facet,
                  in ODPoint where);
The potential destination part then decides whether it can receive the dragged object. If it accepts the data, it either incorporates it or embeds it. This section describes how to design your drop method, how to accept a drop of non-
OpenDoc data, and what the source part (the part that initiated the drag) should do after the drop occurs.

Drag Attributes and the Drop Method

The OpenDoc human interface guidelines specify when a drop should be considered a move and when it should be considered a copy; see "Dropping". The destination part can inspect the OpenDoc drag attributes (by calling the GetDragAttributes method of the drag-and-drop object) to determine how to handle the drop. Drag attributes are bit flags, and more than one can be set at a time. Table 8-2 lists the defined OpenDoc drag attributes. The first two apply during the drag operation; the rest apply at the drop.
Table 8-2 Drag attributes
ConstantDescription
kODDragIsInSourceFrameThe item being dragged has not yet left the source frame of the drag.
kODDragIsInSourcePartThe item being dragged has not yet left the source part of the drag.
kODDropIsInSourceFrameThe drop is occurring in the source frame
of the drag.
kODDropIsInSourcePartThe drop is occurring in the part displayed in the source frame of the drag (though not necessarily in the source frame itself).
kODDropIsMoveThis drag-and-drop operation is a move.
kODDropIsCopyThis drag-and-drop operation is a copy.
kODDropIsPasteAsThe destination part should display the Paste As dialog box (the user has held down the Command key while dropping).

In your Drop method, you might follow steps similar to these:

  1. Use the ODDragItemIterator passed to you to determine whether you can handle the drop, as described in the section "On Entering a Part's Facet". (Alternatively, you can set a "can-drop" flag in DragEnter or DragWithin that you inspect at this point.)
  2. Examine the drag attributes to see, for example, whether this is a move or a copy and whether or not to display the Paste As dialog box.
  3. If the drag attribute kODDropIsPasteAs is set (that is, if the user held down the Command key when the drop occurred), take these steps before reading from the drag-and-drop object:

    • Call the ShowPasteAsDialog method of the drag-and-drop object to display a Paste As dialog box (see Figure 8-3).
    • If the ShowPasteAsDialog method returns a result of true, the user has pressed the OK button; use the results of the interaction (passed back as a structure of type ODPasteAsResult) to determine what action to take. The section "Handling the Paste As Dialog Box" lists the kinds of pasting that the user can specify.

  4. Depending on the nature of the data and the user's instructions, you may either incorporate the data or embed it, you may first translate it, and you may create a link to its source.

    • If you are creating a link, follow the steps in "Creating a Link at the Destination". Take into account the automatic/manual update setting, as well as the other Paste As settings chosen by the user.
    • If you are translating but not creating a link, follow the steps in "Translating Before Incorporating or Embedding".
    • If you are simply incorporating the data, follow the steps in "Incorporating Intrinsic Content".
    • If you are simply embedding the data, follow the steps in "Embedding a Single Part".

      In all of these cases, you initially read the data from the drag-and-drop object, and you specify the following kinds of clone transactions:

    • If a move is specified (the drag attribute kODDropIsMove is set), use a clone transaction of kODCloneDropMove--unless the user has specified that a link be created. Creating a link takes precedence over specifying a drag-move; therefore, use a clone transaction of kODCloneDropCopy in this case.
    • If a copy is specified (the drag attribute kODDropIsCopy is set), use a clone transaction of kODCloneDropCopy

Note
The destination part can override a drag that is a move and force it to be a copy. It cannot, however, override a copy and force it to be a move.
  1. Add a single action to the action history (see "Adding an Action to the Undo Action History" describes why this action must be a single-stage action.

    If the drop involves an embedded frame, follow the instructions listed in Table 6-4: save the current value of the frame's in-limbo flag and then set the flag to false.

  2. If it is not already active, activate the frame in which the drop occurred, and select the dropped content.
  3. Notify OpenDoc and your containing part that there has been a change to your part's content; see "Making Content Changes Known".

When it completes, your Drop method should return an appropriate result (of type ODDropResult), such as kODDropCopy, kODDropMove, or kODDropFail. OpenDoc in turn passes that information on to the source part; see "Completion of StartDrag".

Accepting Non-OpenDoc Data

When your part's Drop method is called, it is passed a drag-item iterator so that you can access all drag items in the drag-and-drop object. If OpenDoc data has been dragged, there is only one drag item in the object. If the data comes from outside of OpenDoc (such as from the Mac OS Finder), there may be more than one item. It is the responsibility of the destination part to iterate through all the drag items to find out whether it can accept the drop.

You can examine the contents property of the storage unit of each dragged item to determine the kind of data the property contains. On the Mac OS, for example, if the drag was initiated in the Finder and the dragged item is a file, the contents property of the item's storage unit contains a value whose type is the OpenDoc ISO prefix followed by "MacOS:OSType:FileType:" followed by the Mac OS file-type designation. If the item is a text file, for example, the value type is (ISO prefix plus) "MacOS:OSType:FileType:TEXT". If you can read data of that type, you can incorporate it into your part. Otherwise, you may be able to embed it as a separate part.

For more information, see the Mac OS Drag Manager documentation and Inside Macintosh: Files.

Dragging Non-OpenDoc Data to the Finder

You may want to drag data to the Finder (or other application that can accept a file) yet not save it as an OpenDoc document. (For example, you may want to save data in a text file rather than an OpenDoc Bento file.) In this case, follow these steps:

  1. Before you call StartDrag, create a PromiseHFSFlavor structure and set the file type, file creator, and finder flag fields for the file that you wish to create.
  2. Write out a promise of that structure data (type kODHFSPromise) to the content storage unit of the drag-and-drop object.
  3. When you call your part's FulfillPromise method during the drop, check whether a kODHFSPromise type value exists in the storage-unit view passed to your FulfillPromise method. If so, the type will specify an FSSpec field that contains the location of the drop. Read the FSSpec information from that storage-unit view and use platform-specific calls to create a new file and write out your data to that file. Be sure to set the file type and file creator of the file appropriately and reset the offset of the storage-unit view to 0.

Completion of StartDrag

If your part is the source part of this drag, your call to the drag-and-drop object's StartDrag method completes after the destination's Drop method completes. You can now, based on the return value of StartDrag, confirm whether the operation was a move or a copy or whether it failed. Take
these steps:

  1. Whether the operation was a move or a copy, add an ending action to the undo action history (see "Adding Multistage Actions".

    If the operation began as a drag-move but ended up being a drag-copy, and if it involved an embedded frame, reset the frame's in-limbo flag to false. See Table 6-4.

  2. If it was a move, delete the dragged content from your part and notify OpenDoc and your containing part that your content has changed. See "Making Content Changes Known".
  3. Whether it was a move or a copy, call your source frame's SetDragging method once again, this time passing it a value of kODFalse to notify OpenDoc that the frame can once more be a drop target.

Asynchronous drag and drop
Version 1.0 of OpenDoc on the Mac OS does not support asynchronous drag and drop. For future compatibility, however, OpenDoc provides the ODPart method DropCompleted. This method is called to notify the source part of the completion of a drag that it initiated. The method provides the same drop results that StartDrag returns for a synchronous drag.
The destPart return value
The destPart value returned by StartDrag is intended for future use only. Although, you can compare object pointers to determine whether a drop occurred in your part, you should not call any of the destination part's methods. The only exception is that your part needs to call the destination part's Release method; see the OpenDoc Class Reference for the Mac OS for more information.

Previous Book Contents Book Index Next

© Apple Computer, Inc.
16 JUL 1996




Navigation graphic, see text links

Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help